package com.blog.blogservice.app.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.blog.blogservice.api.controller.api.dto.CategorySumDTO;
import com.blog.blogservice.api.controller.api.dto.LabelSumDTO;
import com.blog.blogservice.app.service.ArticleService;
import com.blog.blogservice.app.service.CategoryService;
import com.blog.blogservice.app.service.LabelService;
import com.blog.blogservice.domain.entity.Article;
import com.blog.blogservice.domain.entity.ArticleSummary;
import com.blog.blogservice.domain.entity.Category;
import com.blog.blogservice.domain.entity.Label;
import com.blog.blogservice.infra.mapper.ArticleMapper;
import com.blog.blogservice.util.MarkdownToHtmlUtils;
import com.blog.blogservice.util.MinIOUtil;
import com.blog.blogservice.util.RedisUtils;
import com.blog.blogservice.util.constant.BlogConstant;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.collections.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.StringJoiner;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

/**
 * 文章表(Article)应用服务
 *
 * @author guanglin.ma
 * @since 2024-03-02 23:24:54
 */
@Service
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {


    Logger logger = LoggerFactory.getLogger(ArticleServiceImpl.class);

    @Autowired
    private MinIOUtil minIOUtil;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private LabelService labelService;
    @Autowired
    private CategoryService categoryService;

    /**
     * 添加文章
     *
     * @param file
     * @param article
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean addArticle(MultipartFile file, Article article) throws Exception {
        boolean success = false;
//        RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
        // 读取Markdown文件内容
        String markdownContent = new String(file.getBytes());
        String htmlStr = MarkdownToHtmlUtils.splitHtml(markdownContent, article.getArticleTitle());

        MultipartFile multipartFile = toMultipartFile(htmlStr);

        try {
            String url = minIOUtil.putObject(multipartFile);
            article.setArticleContent(url);

            article.setArticleCode(BlogConstant.ARTICLE_CODE_PREFIX + System.currentTimeMillis());


            if (!CollectionUtils.isEmpty(article.getLabelList())) {
                QueryWrapper labelWrapper = new QueryWrapper<>();
                labelWrapper.in("label_id", article.getLabelList().stream()
                        .map(label -> label.getLabelId()).collect(Collectors.toList()));
                labelWrapper.select("label_id", "label_name", "label_code");
                List<Label> labelList = labelService.listQueryWrapper(labelWrapper);

                if (!CollectionUtils.isEmpty(labelList)) {
                    StringJoiner labelIdStringJoiner = new StringJoiner(",");
                    StringJoiner labelNameStringJoiner = new StringJoiner(",");
                    labelList.forEach(val -> {
                        labelIdStringJoiner.add(String.valueOf(val.getLabelId()));
                        labelNameStringJoiner.add(val.getLabelName());
                    });
                    article.setLabelId(labelIdStringJoiner.toString());
                    article.setLabelName(labelNameStringJoiner.toString());
                    article.setLabelList(labelList);
                }
            }
            if (!CollectionUtils.isEmpty(article.getCategoryList())) {
                QueryWrapper categoryWrapper = new QueryWrapper<>();
                categoryWrapper.in("category_id", article.getCategoryList().stream()
                        .map(category -> category.getCategoryId()).collect(Collectors.toList()));
                categoryWrapper.select("category_id", "category_name", "category_code");
                List<Category> categoryList = categoryService.listQueryWrapper(categoryWrapper);

                if (!CollectionUtils.isEmpty(categoryList)) {
                    StringJoiner categoryIdStringJoiner = new StringJoiner(",");
                    StringJoiner categoryNameStringJoiner = new StringJoiner(",");
                    categoryList.forEach(val -> {
                        categoryIdStringJoiner.add(String.valueOf(val.getCategoryId()));
                        categoryNameStringJoiner.add(val.getCategoryName());
                    });
                    article.setCategoryId(categoryIdStringJoiner.toString());
                    article.setCategoryName(categoryNameStringJoiner.toString());
                    article.setCategoryList(categoryList);
                }
            }

            success = this.save(article);


            QueryWrapper queryWrapper = new QueryWrapper<>().eq("article_code", article.getArticleCode());
            Article articleAll = this.getOne(queryWrapper);
            articleAll.setLabelList(article.getLabelList());
            articleAll.setCategoryList(article.getCategoryList());
            String articleJson = objectMapper.writeValueAsString(articleAll);

            RedisUtils.startTransaction(redisTemplate);

            //文章列表
            redisTemplate.opsForHash().put(BlogConstant.ARTICLE_LIST, article.getArticleCode(), articleJson);
            //可见文章 System.currentTimeMillis()
            redisTemplate.opsForZSet().add(BlogConstant.ARTICLE_VISIBLE, article.getArticleCode(), Double.valueOf(String.valueOf(System.currentTimeMillis())));
            // 标签集合
            List<Label> labelList = article.getLabelList();
            labelList.forEach(label -> {
                redisTemplate.opsForZSet().add(String.format(BlogConstant.ARTICLE_LABEL, label.getLabelCode()), articleJson, Double.valueOf(String.valueOf(System.currentTimeMillis())));
            });
            // 分类集合
            List<Category> categoryList = article.getCategoryList();
            categoryList.forEach(category -> {
                redisTemplate.opsForZSet().add(String.format(BlogConstant.ARTICLE_CATEGORY, category.getCategoryCode()), articleJson, Double.valueOf(String.valueOf(System.currentTimeMillis())));
            });

            RedisUtils.execTransaction(redisTemplate);


        } catch (Exception e) {
            logger.error("上传报错：" + e);

            if (RedisUtils.isInTransaction()) {
                //取消事务操作
                RedisUtils.discardTransaction(redisTemplate);
            }
            List<String> urlAnalysis = MinIOUtil.getUrlAnalysis(article.getArticleContent());
            String bucketName = urlAnalysis.get(urlAnalysis.size() - 2);
            String objectName = urlAnalysis.get(urlAnalysis.size() - 1);
            minIOUtil.deleteFile(bucketName, objectName);
            throw e;
        }
        return success;
    }

    /**
     * 删除文章
     *
     * @param articleId
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteArticle(Integer articleId) throws ExecutionException, InterruptedException {
        List<Article> articleList = this.listByIds(Collections.singletonList(articleId));
        if (!CollectionUtils.isEmpty(articleList)) {
            Article article = articleList.get(0);

            RedisConnection redisConnection = redisTemplate.getConnectionFactory().getConnection();
            this.removeById(articleId);
            try {

                //开启事务
                redisConnection.openPipeline();
                redisConnection.multi();

                //移除可见
                redisConnection.zRem(redisTemplate.getStringSerializer().serialize(BlogConstant.ARTICLE_VISIBLE), redisTemplate.getStringSerializer().serialize(article.getArticleCode()));
                //添加到不可见
                redisConnection.zAdd(redisTemplate.getStringSerializer().serialize(BlogConstant.ARTICLE_INVISIBLE), Double.valueOf(Instant.now().toEpochMilli()), redisTemplate.getStringSerializer().serialize(article.getArticleCode()));

                // 提交事务
                redisConnection.exec();

            } catch (Exception e) {
                redisConnection.discard();
                logger.error(e.toString());
                throw e;
            } finally {
                redisConnection.close();
            }

        }
    }

    /**
     * 文章列表
     *
     * @param article
     * @return
     */
    @Override
    public List<Article> listArticle(Article article) {
        QueryWrapper queryWrapper = new QueryWrapper<>();
        return list(queryWrapper);
    }

    @Override
    public List<Article> articleApiList(Article article) {

        //这里redis存在网络延迟 后期修改为lua
        Set<String> articleCodeList = redisTemplate.opsForZSet().range(BlogConstant.ARTICLE_VISIBLE, 0, -1);
        List<String> articleList = redisTemplate.opsForHash().multiGet(BlogConstant.ARTICLE_LIST, articleCodeList);
        List<Article> articles = new ArrayList<>(2);
        articleList.forEach(str -> {
            try {
                Article article1 = objectMapper.readValue(str, Article.class);
                articles.add(article1);
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }

        });
        return articles;
    }

    /**
     * 文章 标签 分类 汇总
     *
     * @return
     */
    @Override
    public ArticleSummary articleSummary() {

        List<Object> results = redisTemplate.executePipelined((RedisCallback<Object>) connection -> {

            Optional.ofNullable(connection.hLen(BlogConstant.LABEL_LIST.getBytes())).orElse(0L);
            Optional.ofNullable(connection.hLen(BlogConstant.CATEGORY_LIST.getBytes())).orElse(0L);
            Optional.ofNullable(connection.hLen(BlogConstant.ARTICLE_LIST.getBytes())).orElse(0L);
            connection.hGetAll(BlogConstant.LABEL_LIST.getBytes());
            connection.hGetAll(BlogConstant.CATEGORY_LIST.getBytes());

            return null;
        });

        ArticleSummary articleSummary = new ArticleSummary();
        articleSummary.setArticleLabelNum(String.valueOf(results.get(0)));
        articleSummary.setArticleCategoryNum(String.valueOf(results.get(1)));
        articleSummary.setArticleNum(String.valueOf(results.get(2)));
        articleSummary.setLabelSumList(new ArrayList<>());
        articleSummary.setCategorySumList(new ArrayList<>());

        Map labelMap = (Map) results.get(3);
        Map categoryMap = (Map) results.get(4);

        if (!MapUtils.isEmpty(labelMap)) {
            labelMap.forEach((k, v) -> {
                LabelSumDTO labelSum = new LabelSumDTO();
                labelSum.setLabelCode(String.valueOf(k));
                labelSum.setLabelName(String.valueOf(v));
                articleSummary.getLabelSumList().add(labelSum);
            });
        }
        if (!MapUtils.isEmpty(categoryMap)) {
            categoryMap.forEach((k, v) -> {
                CategorySumDTO categorySum = new CategorySumDTO();
                categorySum.setCategoryCode(String.valueOf(k));
                categorySum.setCategoryName(String.valueOf(v));
                articleSummary.getCategorySumList().add(categorySum);
            });
        }

        articleSummary.getLabelSumList().forEach(v -> {
            Long num = redisTemplate.opsForZSet().size(String.format(BlogConstant.ARTICLE_LABEL, v.getLabelCode()));
            v.setLabelNum(num);

        });
        articleSummary.getCategorySumList().forEach(v -> {
            Long num = redisTemplate.opsForZSet().size(String.format(BlogConstant.ARTICLE_CATEGORY, v.getCategoryCode()));
            v.setCategoryNum(num);

        });

        return articleSummary;
    }

    /**
     * 内容封装MultipartFile
     *
     * @param htmlStr
     * @return
     */
    private MultipartFile toMultipartFile(String htmlStr) {

        // 创建临时文件
        File tempFile = null;
        MultipartFile multipartFile = null;
        try {
            tempFile = File.createTempFile("file", ".html");
            FileWriter writer = new FileWriter(tempFile);
            writer.write(htmlStr);
            writer.flush();
            writer.close();
            byte[] bytes = Files.readAllBytes(tempFile.toPath());
            multipartFile = new MockMultipartFile("file", // 表单字段名称
                    tempFile.getName(), // 文件原始名称
                    "text/html", // 内容类型
                    bytes // 文件内容的字节数组
            );
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return multipartFile;
    }

    @Override
    public List<Article> articleByTypeCode(String type, String code) {

        List<Article> articleHashSet = new ArrayList<>();
        List<Object> results = redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            if ("label".equals(type)) {
                String key = String.format(BlogConstant.ARTICLE_LABEL, code);
                connection.zRange(key.getBytes(), 0, -1);
            } else if ("category".equals(type)) {
                String key = String.format(BlogConstant.ARTICLE_CATEGORY, code);
                connection.zRange(key.getBytes(), 0, -1);
            }
            connection.zRange(BlogConstant.ARTICLE_VISIBLE.getBytes(), 0, -1);
            return null;
        });

        if (!CollectionUtils.isEmpty(results)) {

            Set<String> articleSet = (Set<String>) results.get(0);
            Set<String> visibleSet = (Set<String>) results.get(1);

            if (!CollectionUtils.isEmpty(articleSet) && !CollectionUtils.isEmpty(visibleSet)) {

                for (String articleStr : articleSet) {
                    Article article = null;
                    try {
                        article = objectMapper.readValue(articleStr, Article.class);
                    } catch (JsonProcessingException e) {
                        logger.error(e.toString());
//                        throw new RuntimeException(e);
                    }
                    if (visibleSet.contains(article.getArticleCode())) {
                        articleHashSet.add(article);
                    }
                }
            }
        }
        return articleHashSet;
    }
}

